﻿using System;
using System.IO;
using Browis.Utils;
using log4net;
using System.Collections.Generic;


namespace Browis.Package
{
    public class DataPackage
    {
        /// <summary>
        /// 定义包头大小
        /// </summary>
        private const int PackageHeaderSize = 64;

        private static object lockObj = new object();

        private Stream pakStream;
        private Int64 length;
        private UInt32 crc;
        private UInt32 dosTime;
        private UInt32 cmdCount;
        private ILog log = null;

        public UInt32 CRC
        {
            get
            {
                if (pakStream.CanWrite == true)
                {
                    Byte[] buffer = new Byte[4096];

                    long position = pakStream.Position;
                    //流的位置置为包头信息后，并用HashStream读取流内包头信息后的全部数据
                    pakStream.Position = PackageHeaderSize;

                    lock (lockObj)
                    {
                        HashStream hs = new HashStream(pakStream, true, new CRC32());
                        while (hs.Read(buffer, 0, buffer.Length) > 0) ;
                        buffer = hs.Checksum;
                        hs.Close();
                    }

                    pakStream.Position = position;
                    return BitConverter.ToUInt32(buffer, 0);
                }
                else
                    return crc;
            }
        }

        public Int64 Length
        {
            get
            {
                if (pakStream.CanWrite == true)
                    return pakStream.Length;
                else
                    return length;
            }
        }

        public UInt32 HeaderLength
        {
            get;
            private set;
        }

        public DateTime DosTime
        {
            get
            {
                if (pakStream.CanWrite == true)
                    return DateTime.Now;
                else
                    return ToDateTime(dosTime);
            }
        }

        /// <summary>
        /// 子库标志
        /// </summary>
        public string DBName
        {
            set;
            get;
        }

        /// <summary>
        /// 包是否保存在事务中
        /// </summary>
        public bool CancelTransaction
        {
            get;
            set;
        }

        private static UInt32 GetDosTime(DateTime now)
        {
            return (UInt32)((now.Second / 2) | (now.Minute << 5) | (now.Hour << 11) | (now.Day << 16) | (now.Month << 21) | ((now.Year - 1980) << 25));
        }

        private static DateTime ToDateTime(UInt32 time)
        {
            return new DateTime((int)(time >> 25) + 1980, (int)(time >> 21) & 0xf, (int)(time >> 16) & 0x1f, (int)(time >> 11) & 0xf, (int)(time >> 5) & 0x3f, (int)(time & 0x1f) * 2);
        }

        /// <summary>
        /// 确认数据包的所有数据已经写出到流Writer中。
        /// </summary>
        private void Flush()
        {
            if (pakStream.CanWrite == false)
                return;

            length = (UInt32)pakStream.Length;
            crc = CRC;
            dosTime = GetDosTime(DateTime.Now);

            pakStream.Seek(0, SeekOrigin.Begin);
            pakStream.Write(BitConverter.GetBytes(crc), 0, 4);
            pakStream.Write(BitConverter.GetBytes(pakStream.Length), 0, 8);
            pakStream.Write(BitConverter.GetBytes(PackageHeaderSize), 0, 4);
            pakStream.Write(BitConverter.GetBytes(dosTime), 0, 4);
            pakStream.Write(BitConverter.GetBytes(cmdCount), 0, 4);

            Byte[] temp = System.Text.Encoding.UTF8.GetBytes(DBName);
            pakStream.Write(temp, 0, temp.Length > 28 ? 28 : temp.Length);

            pakStream.Seek(52, SeekOrigin.Begin);
            pakStream.Write(BitConverter.GetBytes(CancelTransaction), 0, 1);
        }

        protected DataPackage()
        {
            log = LogManager.GetLogger("DBToDBPub");           
        }

        public static DataPackage Create(Stream writer)
        {
            DataPackage instance = new DataPackage();
            instance.pakStream = writer;
            instance.pakStream.SetLength(PackageHeaderSize);
            instance.pakStream.Seek(0, SeekOrigin.End);

            instance.HeaderLength = PackageHeaderSize;
            instance.cmdCount = 0;
            instance.CancelTransaction = false;
            return instance;
        }

        public static DataPackage Open(string fileName, Stream pakReader)
        {
            DataPackage instance = new DataPackage();

            instance.pakStream = pakReader;
            //检查包文件格式
            if (instance.pakStream.Length < PackageHeaderSize)
            {
                instance.log.WarnFormat("包文件{0}长度异常！", fileName);
                throw new FormatException(fileName);
            }
            byte[] buffer = new byte[PackageHeaderSize];
            pakReader.Read(buffer, 0, PackageHeaderSize);

            instance.crc = BitConverter.ToUInt32(buffer, 0);
            instance.length = BitConverter.ToInt64(buffer, 4);
            instance.HeaderLength = BitConverter.ToUInt32(buffer, 12);
            instance.dosTime = BitConverter.ToUInt32(buffer, 16);
            instance.cmdCount = BitConverter.ToUInt32(buffer, 20);
            instance.DBName = System.Text.Encoding.UTF8.GetString(buffer, 24, 28);
            instance.CancelTransaction = BitConverter.ToBoolean(buffer, 52);

            if (instance.Length != instance.pakStream.Length)
            {
                instance.log.WarnFormat("包文件{0}长度异常，可能已经被破坏！", fileName);
                throw new Exception("流长度错误");
            }

            //对包进行CRC校验
            lock (lockObj)
            {
                HashStream hs = new HashStream(instance.pakStream, true, new CRC32());
                instance.pakStream.Seek(instance.HeaderLength, SeekOrigin.Begin);

                buffer = new Byte[4096];
                while (hs.Read(buffer, 0, buffer.Length) > 0) ;
                buffer = hs.Checksum;
                hs.Close();
            }

            if (BitConverter.ToUInt32(buffer, 0) != instance.CRC)
            {
                instance.log.WarnFormat("包文件{0}的CRC校验错误，可能已经被破坏！", fileName);
                throw new Exception("CRC校验错误");
            }
            instance.pakStream.Seek(instance.HeaderLength, SeekOrigin.Begin);

            return instance;
        }


        public void Close()
        {
            Flush();
            //pakStream.Close();
        }

        /// <summary>
        /// 为数据包创建命令节
        /// </summary>
        /// <param name="name">命令名称</param>
        /// <param name="options">操作类型</param>
        /// <returns>命令节的流</returns>
        public SectionStream CreatCommand(string name, UInt32 options)
        {
            cmdCount += 1;
            return SectionStream.Create(pakStream, name, options, log);
        }

        public SectionStream CreateCmdNestSection(String name, UInt32 options)
        {
            return SectionStream.Create(pakStream, name, options, log);
        }

        public SectionStream LookupCommand(string name)
        {
            pakStream.Seek(HeaderLength, SeekOrigin.Begin);
            return SectionStream.Lookup(pakStream, name, log);
        }

        /// <summary>
        /// 从包流的当前位置打开一个命令节
        /// </summary>
        /// <returns>命令节流</returns>
        public SectionStream OpenCommand()
        {
            return SectionStream.Open(pakStream);
        }

        /// <summary>
        /// 查找嵌套的Section流
        /// </summary>
        /// <param name="sCmdName">命令名称</param>
        /// <param name="lstSecName">按顺序保留待查找的节名称，必须按照从外层节到内层节的顺序添加</param>
        /// <returns>待查找的节</returns>
        public SectionStream LookupCmdNestSection(String sCmdName,List<string> lstSecName)
        {
            pakStream.Seek(HeaderLength, SeekOrigin.Begin);
            SectionStream curStream = SectionStream.Lookup(pakStream, sCmdName, log);
            for (int i = 0; i < lstSecName.Count; i++)
            {
                curStream = SectionStream.Lookup(curStream, lstSecName[i], log);
            }

            return curStream;// SectionStream.Lookup(pakStream, name, log);
        }

    }

    [Serializable]
    public class CommandValue
    {
        public string ID { get; set; }
        public string Type { get; set; }
        public uint Length { get; set; }
        public bool AllowNull { get; set; }
    }

    [Serializable]
    public class ParameterInfo
    {
        public string Name { get; set; }
        public System.Data.DbType Type { get; set; }
        public int RefIndex { get; set; }
    }


}